package com.wg.core.io;

import cn.hutool.core.util.StrUtil;
import com.wg.core.utils.BitConverter;

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.util.Collection;
import java.util.function.Consumer;

/**
 * 二进制数据写实现
 *
 * <p>实现IBinaryReader接口，提供大端序格式的二进制数据写功能</p>
 *
 * @author 少爷123
 */
public class BigEndianBinaryWriter implements IBinaryWriter {
    private final ByteArrayOutputStream stream;
    private static final short MAX_SHORT_LENGTH = 0x7fff;

    public BigEndianBinaryWriter(int size) {
        this.stream = new ByteArrayOutputStream(Math.max(size, 64));
    }

    public BigEndianBinaryWriter(ByteArrayOutputStream stream) {
        this.stream = stream;
    }

    @Override
    public byte[] toByteArray() {
        return stream.toByteArray();
    }

    @Override
    public void writeBoolean(boolean value) {
        writeToStream(() -> BitConverter.getBytes(value));
    }

    @Override
    public void writeBooleanArray(boolean[] values) {
        if (values == null) {
            writeShort((short) 0);
            return;
        }

        writeShort((short) values.length);
        for (boolean value : values) {
            writeBoolean(value);
        }
    }

    @Override
    public void writeBooleanCollection(Collection<Boolean> collection) {
        writeCollection(collection, this::writeBoolean);
    }

    @Override
    public void writeByte(byte value) {
        stream.write(value);
    }

    @Override
    public void writeRawBytes(byte[] value) {
        if (value != null) {
            stream.write(value, 0, value.length);
        }
    }

    @Override
    public void writeBytes(byte[] value) {
        if (value == null) {
            writeInt(0);
            return;
        }
        writeInt(value.length);
        stream.write(value, 0, value.length);
    }

    @Override
    public void writeChar(char value) {
        writeToStream(() -> BitConverter.getBytes(value));
    }

    @Override
    public void writeCharArray(char[] values) {
        if (values == null) {
            writeShort((short) 0);
            return;
        }

        writeShort((short) values.length);
        for (char value : values) {
            writeChar(value);
        }
    }

    @Override
    public void writeDouble(double value) {
        writeToStream(() -> BitConverter.getBytes(value));
    }

    @Override
    public void writeDoubleArray(double[] values) {
        if (values == null) {
            writeShort((short) 0);
            return;
        }

        writeShort((short) values.length);
        for (double value : values) {
            writeDouble(value);
        }
    }

    @Override
    public void writeDoubleCollection(Collection<Double> collection) {
        writeCollection(collection, this::writeDouble);
    }

    @Override
    public void writeShort(short value) {
        writeToStream(() -> BitConverter.getBytes(value));
    }

    @Override
    public void writeShortArray(short[] values) {
        if (values == null) {
            writeShort((short) 0);
            return;
        }

        writeShort((short) values.length);
        for (short value : values) {
            writeShort(value);
        }
    }

    @Override
    public void writeShortCollection(Collection<Short> collection) {
        writeCollection(collection, this::writeShort);
    }

    @Override
    public void writeInt(int value) {
        writeToStream(() -> BitConverter.getBytes(value));
    }

    @Override
    public void writeBigInteger(BigInteger value) {
        if (value == null) {
            writeString("");
        } else {
            writeString(value.toString());
        }
    }

    @Override
    public void writeIntArray(int[] values) {
        if (values == null) {
            writeShort((short) 0);
            return;
        }

        writeShort((short) values.length);
        for (int value : values) {
            writeInt(value);
        }
    }

    @Override
    public void writeIntCollection(Collection<Integer> collection) {
        writeCollection(collection, this::writeInt);
    }

    @Override
    public void writeLong(long value) {
        writeToStream(() -> BitConverter.getBytes(value));
    }

    @Override
    public void writeLongArray(long[] values) {
        if (values == null) {
            writeShort((short) 0);
            return;
        }

        writeShort((short) values.length);
        for (long value : values) {
            writeLong(value);
        }
    }

    @Override
    public void writeLongCollection(Collection<Long> collection) {
        writeCollection(collection, this::writeLong);
    }

    @Override
    public void writeFloat(float value) {
        writeToStream(() -> BitConverter.getBytes(value));
    }

    @Override
    public void writeFloatArray(float[] values) {
        if (values == null) {
            writeShort((short) 0);
            return;
        }

        writeShort((short) values.length);
        for (float value : values) {
            writeFloat(value);
        }
    }

    @Override
    public void writeFloatCollection(Collection<Float> collection) {
        writeCollection(collection, this::writeFloat);
    }

    @Override
    public void writeString(String value) {
        if (StrUtil.isEmpty(value)) {
            writeShort((short) 0);
            return;
        }

        byte[] bytes = BitConverter.getBytes(value);
        int length = bytes.length;

        if (length <= MAX_SHORT_LENGTH) {
            writeShort((short) length);
        } else {
            writeShort((short) -1); // 特殊标记表示长字符串
            writeInt(length);
        }
        stream.write(bytes, 0, length);
    }

    @Override
    public void writeStringArray(String[] values) {
        writeObjectArray(values, this::writeString);
    }

    @Override
    public void writeStringCollection(Collection<String> collection) {
        writeCollection(collection, this::writeString);
    }

    @Override
    public <T extends IBinaryWritable> void writeObject(T object) {
        if (object != null) {
            writeRawBytes(object.toBytes());
        }
    }

    @Override
    public <T extends IBinaryWritable> void writeObjectArray(T[] array) {
        writeObjectArray(array, this::writeObject);
    }

    @Override
    public <T extends IBinaryWritable> void writeObjectCollection(Collection<T> collection) {
        writeCollection(collection, this::writeObject);
    }

    @Override
    public ByteArrayOutputStream baseStream() {
        return stream;
    }

    // ============= 通用方法实现 =============

    /**
     * 通用集合写入方法<br>
     * 先写入集合大小（short类型，大端序），再通过elementWriter逐个写入集合元素<br>
     * 若集合为null，写入长度为0的short标记
     *
     * @param collection    要写入的集合（可能为null）
     * @param elementWriter 元素写入逻辑（Consumer函数式接口，接收集合元素并写入）
     * @param <T>           集合元素类型
     */
    private <T> void writeCollection(Collection<T> collection, Consumer<T> elementWriter) {
        if (collection == null) {
            writeShort((short) 0);
            return;
        }

        writeShort((short) collection.size());
        for (T element : collection) {
            elementWriter.accept(element);
        }
    }

    /**
     * 通用对象数组写入方法<br>
     * 先写入数组长度（short类型，大端序），再通过elementWriter逐个写入数组元素<br>
     * 若数组为null，写入长度为0的short标记
     *
     * @param array         要写入的对象数组（元素需支持elementWriter，可能为null）
     * @param elementWriter 元素写入逻辑（Consumer函数式接口，接收数组元素并写入）
     * @param <T>           数组元素类型
     */
    private <T> void writeObjectArray(T[] array, Consumer<T> elementWriter) {
        if (array == null) {
            writeShort((short) 0);
            return;
        }

        writeShort((short) array.length);
        for (T element : array) {
            elementWriter.accept(element);
        }
    }

    /**
     * 通用字节写入方法（通过函数式接口获取字节数组）<br>
     * 调用byteSupplier获取待写入的字节数组，并将其写入底层流
     *
     * @param byteSupplier 提供字节数组的函数式接口（无参数，返回byte[]）
     */
    private void writeToStream(ByteArraySupplier byteSupplier) {
        byte[] bytes = byteSupplier.get();
        stream.write(bytes, 0, bytes.length);
    }

    @FunctionalInterface
    private interface ByteArraySupplier {
        byte[] get();
    }
}